﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Microsoft.WindowsAzure.StorageClient;
using AntsCode.StorageClientExtensions.Helpers;
using AntsCode.StorageClientExtensions.QueryHandlers;
using System.Data.Services.Client;

namespace AntsCode.StorageClientExtensions
{
    internal class XTableServiceVisitor : AntsCode.StorageClientExtensions.Helpers.ExpressionVisitor
    {
        private QueryExecutor queryExecutor;

        public XTableServiceVisitor(TableServiceContext context, EventHandler<XTableServiceSendingRequestEventArgs> sendingRequest, RetryPolicy retryPolicy,
            Expression expression, ContinuationToken continuationToken)
        {
            this.Context = context;
            this.Expression = expression;
            this.ContinuationToken = continuationToken;

            queryExecutor = new QueryExecutor(sendingRequest, retryPolicy);

            this.Visit(expression);
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            QueryHandler queryHandler = null;

            if (m.Method.DeclaringType == typeof(Queryable))
            {
                // Visit any sub expressions
                this.Visit(m.Arguments[0]);

                switch (m.Method.Name)
                {
                    case "Where":
                        queryHandler = this.GetWhereHandler(m);
                        break;

                    case "Select":
                        queryHandler = this.GetSelectHandler(m);
                        break;

                    case "Join":
                    case "GroupJoin":
                        queryHandler = new JoinQueryHandler(this.Context, this.queryExecutor, this.Expression, m, (IQueryable)this.Data, this.ContinuationToken);
                        break;

                    case "SelectMany":
                        // Handled within JoinQueryHandler    
                        break;

                    case "First":
                        queryHandler = this.GetFirstHandler(m, false);
                        break;

                    case "FirstOrDefault":
                        queryHandler = this.GetFirstHandler(m, true);
                        break;

                    case "Take":
                        queryHandler = this.GetTakeHandler(m);
                        break;

                    case "OrderBy":
                        queryHandler = this.GetOrderByHandler(m, OrderByType.OrderBy);
                        break;

                    case "OrderByDescending":
                        queryHandler = this.GetOrderByHandler(m, OrderByType.OrderByDescending);
                        break;

                    case "ThenBy":
                        queryHandler = this.GetOrderByHandler(m, OrderByType.ThenBy);
                        break;

                    case "ThenByDescending":
                        queryHandler = this.GetOrderByHandler(m, OrderByType.ThenByDescending);
                        break;

                    default:
                        throw new ApplicationException("Method '" + m.Method.Name + "' not supported by XTableServiceContext.");
                }

                if (queryHandler != null)
                {
                    this.Data = queryHandler.Data;
                    this.ContinuationToken = queryHandler.ContinuationToken;
                }
            }

            return m;
        }

        private QueryHandler GetWhereHandler(MethodCallExpression m)
        {
            // Get the types required to create the WhereQueryHandler
            Type sourceType = TypeSystem.GetElementType(m.Arguments[0].Type);

            return (QueryHandler)Activator.CreateInstance(typeof(WhereQueryHandler<>).MakeGenericType(sourceType), new object[] { this.Context, this.queryExecutor, this.Expression, m, this.Data, this.ContinuationToken });
        }

        private QueryHandler GetSelectHandler(MethodCallExpression m)
        {
            // Get the types required to create the SelectQueryHandler
            Type sourceType = TypeSystem.GetElementType(m.Arguments[0].Type);

            UnaryExpression ue = (UnaryExpression)m.Arguments[1];
            Expression expr = (Expression)ue.Operand;
            Type returnType = expr.Type.GetGenericArguments()[1];

            return (QueryHandler)Activator.CreateInstance(typeof(SelectQueryHandler<,>).MakeGenericType(sourceType, returnType), new object[] { this.Context, this.queryExecutor, this.Expression, m, this.Data, this.ContinuationToken });
        }

        private QueryHandler GetOrderByHandler(MethodCallExpression m, OrderByType type)
        {
            // Get the types required to create the SelectQueryHandler
            Type sourceType = TypeSystem.GetElementType(m.Arguments[0].Type);

            UnaryExpression ue = (UnaryExpression)m.Arguments[1];
            Expression expr = (Expression)ue.Operand;
            Type orderByType = expr.Type.GetGenericArguments()[1];

            return (QueryHandler)Activator.CreateInstance(typeof(OrderByQueryHandler<,>).MakeGenericType(sourceType, orderByType), new object[] { this.Context, this.queryExecutor, this.Expression, m, this.Data, this.ContinuationToken, type });
        }

        private QueryHandler GetFirstHandler(MethodCallExpression m, bool allowDefault)
        {
            // Get the types required to create the FirstQueryHandler
            Type sourceType = TypeSystem.GetElementType(m.Arguments[0].Type);

            return (QueryHandler)Activator.CreateInstance(typeof(FirstQueryHandler<>).MakeGenericType(sourceType), new object[] { this.Data, this.ContinuationToken, allowDefault });
        }

        private QueryHandler GetTakeHandler(MethodCallExpression m)
        {
            // Get the types required to create the FirstQueryHandler
            Type sourceType = TypeSystem.GetElementType(m.Arguments[0].Type);

            return (QueryHandler)Activator.CreateInstance(typeof(TakeQueryHandler<>).MakeGenericType(sourceType), new object[] { this.queryExecutor, m, this.Data, this.ContinuationToken });
        }

        private object GetPropertyValue(object src, string propName)
        {
            return src.GetType().GetProperty(propName).GetValue(src, null);
        }

        public object Data
        {
            get;
            private set;
        }

        public Expression Expression
        {
            get;
            private set;
        }

        public TableServiceContext Context
        {
            get;
            private set;
        }

        public ContinuationToken ContinuationToken
        {
            get;
            set;
        }
    }
}